import { IFiltersController, ISqlable, TFilter } from ".";
import { TSqlAnalyzeService } from "@/services/sqlAnalyzeServices";

import { TComponentServices } from "@/services/componentServices";
import { TDbServices } from "@/services/dbServices";

type TServices = {
  sqlAnalyze: TSqlAnalyzeService;
  component: TComponentServices;
  db: TDbServices;
};

function filterSet() {
  const map = new Map<string, string>();

  function add(key: string, expression: string): void {
    if (!map.has(key)) {
      throw new Error(`not found updateId[${key}]`);
    }
    map.set(key, expression);
  }
  function remove(cpid: string): void {
    map.delete(cpid);
  }

  function getAllFilters(): TFilter[] {
    return Array.from(map.entries()).map(([key, value]) => {
      return { cpid: key, expression: value };
    });
  }

  return {
    add,
    remove,
    getAllFilters,
  };
}

export function createDataSource(
  name: string,
  services: TServices
): IFiltersController & ISqlable {
  const { add: addFilter, remove: removeFilter, getAllFilters } = filterSet();

  function toSql(exclude: string[], noFilters = false): string {
    const sql = `select * from ${name}`;
    if (noFilters) {
      return sql;
    }

    const excludeSet = new Set(exclude);

    const whereExpr = Array.from(getAllFilters())
      .filter((f) => !excludeSet.has(f.cpid) && !!f.expression)
      .map((f) => f.expression)
      .join(" and ");

    if (whereExpr) {
      return `${sql} where ${whereExpr}`;
    }

    return sql;
  }

  function getName() {
    return name;
  }

  return {
    addFilter,
    removeFilter,
    getAllFilters,
    toSql,
    getName,
  };
}

export function createDataView(
  name: string,
  sql: string,
  services: TServices,
  excludeLinkages: string[] = []
): IFiltersController & ISqlable {
  const { add: addFilter, remove: removeFilter, getAllFilters } = filterSet();
  const mLinkageDatasets = [] as ISqlable[];

  const excludeLinkagesSet = new Set(excludeLinkages);

  function toSql(exclude: string[], noFilters = false): string {
    let sqlCopy = sql;
    if (noFilters) {
      return sql;
    }
    const excludeSet = new Set(exclude);

    mLinkageDatasets.forEach((ds) => {
      const noFilters = excludeLinkagesSet.has(ds.getName());
      const dsSql = ds.toSql(exclude, noFilters);
      sqlCopy = services.sqlAnalyze.replaceTableToFilterQuery(
        sqlCopy,
        ds.getName(),
        dsSql
      );
    });

    const whereExpr = Array.from(getAllFilters())
      .filter((f) => !excludeSet.has(f.cpid) && !!f.expression)
      .map((f) => f.expression)
      .join(" and ");

    if (whereExpr) {
      return `${sql} where ${whereExpr}`;
    }

    return sql;
  }

  function addLinkageDataset(dataset: ISqlable) {
    mLinkageDatasets.push(dataset);
  }

  function getName() {
    return name;
  }

  return {
    addFilter,
    removeFilter,
    getAllFilters,
    toSql,
    addLinkageDataset,
    getName,
  };
}
